package com.esri.hadoop.hive;

import java.util.ArrayList;
import java.util.Collections;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.hive.ql.exec.Description;
import org.apache.hadoop.hive.ql.exec.UDAF;
import org.apache.hadoop.hive.ql.exec.UDAFEvaluator;
import org.apache.hadoop.hive.ql.metadata.HiveException;
import org.apache.hadoop.io.BytesWritable;

import com.esri.core.geometry.Geometry;
import com.esri.core.geometry.GeometryEngine;
import com.esri.core.geometry.SpatialReference;
import com.esri.core.geometry.ogc.OGCGeometry;

@Description(
		name = "ST_Aggr_ConvexHull",
		value = "_FUNC_(ST_Geometry) - aggregate convex hull of all geometries passed",
		extended = "Example:\n"
			+ "  SELECT _FUNC_(geometry) FROM source; -- return convex hull of all geometries in source"
		)

public class ST_Aggr_ConvexHull extends UDAF {
	static final Log LOG = LogFactory.getLog(ST_Aggr_ConvexHull.class.getName());

	public static class AggrConvexHullBinaryEvaluator implements UDAFEvaluator {

		private int MAX_BUFFER_SIZE = 1000;
		private ArrayList<Geometry> geometries = new ArrayList<Geometry>(MAX_BUFFER_SIZE);
		SpatialReference spatialRef = null;
		int firstWKID = -2;
		
		/*
		 * Initialize evaluator
		 */
		@Override
		public void init() {

			if (geometries.size() > 0){
				geometries.clear();
			}
		}

		/*
		 * Iterate is called once per row in a table
		 */
		public boolean iterate(BytesWritable geomref) throws HiveException {

			if (geomref == null) {
				LogUtils.Log_ArgumentsNull(LOG);
				return false;
			}

			if (firstWKID == -2) {
				firstWKID = GeometryUtils.getWKID(geomref);
				if (firstWKID != GeometryUtils.WKID_UNKNOWN) {
					spatialRef = SpatialReference.create(firstWKID);
				}
			} else if (firstWKID != GeometryUtils.getWKID(geomref)) {
				LogUtils.Log_SRIDMismatch(LOG, geomref, firstWKID);
				return false;
			}

			addGeometryToBuffer(geomref);

			return (geometries.size() != 0);
		}

		/*
		 * Merge the current state of this evaluator with the result of another evaluator's terminatePartial()
		 */
		public boolean merge(BytesWritable other) throws HiveException {
			// for our purposes, merge is the same as iterate
			return iterate(other);
		}

		public BytesWritable terminatePartial() throws HiveException {
			maybeAggregateBuffer(true);
			if (geometries.size() == 1) {
				OGCGeometry rslt = OGCGeometry.createFromEsriGeometry(geometries.get(0), spatialRef);
				return GeometryUtils.geometryToEsriShapeBytesWritable(rslt);
			} else {
				return null;
			}
		}

		/*
		 * Return a geometry that is the aggregation of all geometries added up until this point
		 */
		public BytesWritable terminate() throws HiveException{
			// for our purposes, terminate is the same as terminatePartial
			return terminatePartial();
		}

		private void addGeometryToBuffer(BytesWritable geomref) throws HiveException {
			OGCGeometry ogcGeometry = GeometryUtils.geometryFromEsriShape(geomref);
			addGeometryToBuffer(ogcGeometry.getEsriGeometry());
		}

		private void addGeometryToBuffer(Geometry geom) throws HiveException {
			geometries.add(geom);
			maybeAggregateBuffer(false);
		}

		/*
		 * If the right conditions are met (or force == true), create a convex hull of the geometries
		 * in the current buffer
		 */
		private void maybeAggregateBuffer(boolean force) throws HiveException {

			if (force || geometries.size() > MAX_BUFFER_SIZE){
				Geometry[] geomArray = new Geometry[geometries.size()];
				geometries.toArray(geomArray);
				geometries.clear();

				try {
					//LOG.trace("performing convexHull");
					Geometry[] convexResult = GeometryEngine.convexHull(geomArray, true);
					Collections.addAll(geometries, convexResult);  // expect one
				} catch (Exception e) {
					LOG.error("exception thrown", e);
				}
			}
		}

	}
}
